﻿using System;

namespace FloppyMaker
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("We still use NASM to compiler for now!");
            Console.WriteLine("将来会换成TTLL");
            Console.WriteLine("First you need to set NASM path");
            var succ = TryGetArgs(args, out string floppyfile, out string bootsrccode, out string bootbinfile);
            if (!succ)
            {
                ShowHelp();
                return;
            }
            succ = TryCreateFloppy(floppyfile);
            if (succ)
            {
                Console.WriteLine("CreateFloopyImage at:" + floppyfile);
            }
            else
            {
                Console.WriteLine("CreateFloopyImage fail:" + floppyfile);
                return;
            }
            if (bootsrccode != null)
            {
                Console.WriteLine("Build at:" + bootsrccode);

                succ = BuildNASM(bootsrccode, out bootbinfile);
                if (succ)
                {
                    Console.WriteLine("build nasm succ.");
                }
                else
                {
                    Console.WriteLine("build nasm fail.");
                    return;
                }
            }
            if (bootbinfile != null)
            {
                succ = WriteBootInfo(floppyfile, bootbinfile);
                if (succ)
                {
                    Console.WriteLine("WriteBootInfo succ.");
                }
                else
                {
                    Console.WriteLine("WriteBootInfo fail.");

                    return;
                }
            }
            Console.WriteLine("== all works done ==");
        }
        static string cmdfile
        {
            get
            {
                bool isWindows = System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows);
                return isWindows ? "cmd" : "bash";
            }
        }
        static void runCommand(string cmd, out string output, out string errinfo)
        {
            System.Diagnostics.ProcessStartInfo info = new System.Diagnostics.ProcessStartInfo();
            info.FileName = cmdfile;
            info.Arguments = "";
            info.RedirectStandardOutput = true;
            info.RedirectStandardInput = true;
            info.RedirectStandardError = true;
            info.CreateNoWindow = true;
            System.Diagnostics.Process p = System.Diagnostics.Process.Start(info);

            p.StandardInput.WriteLine(cmd);
            p.StandardInput.WriteLine("exit");
            p.WaitForExit();

            output = p.StandardOutput.ReadToEnd();
            errinfo = p.StandardError.ReadToEnd();
        }
        static bool BuildNASM(string srcfilename, out string binfilename)
        {
            binfilename = srcfilename + ".bin";
            var listfilename = srcfilename + ".list.txt";
            string cmd = "nasm " + srcfilename + " -o " + binfilename + " -l" + listfilename;
            runCommand(cmd, out string output, out string errinfo);
            if (string.IsNullOrEmpty(errinfo) == false)
            {
                Console.WriteLine(errinfo);
                return false;
            }
            return true;
        }
        static bool WriteBootInfo(string diskfilename, string binfilename)
        {
            var bin = System.IO.File.ReadAllBytes(binfilename);
            if (bin == null || bin.Length < 0x40 || bin.Length == 511 || bin[0] != 0xEB || bin[1] < 0x3A)
            {
                Console.WriteLine("Error Bin file");
                return false;
            }
            else if (bin.Length > 512)
            {
                Console.WriteLine("binfile is too long.must<=512");
                return false;
            }
            else if (bin.Length == 512)
            {
                if (bin[510] == 0x55 && bin[511] == 0xAA)
                {
                    Console.WriteLine("不需要在代码里写启动标志，这个程序会自动处理");
                }
                else
                {
                    Console.WriteLine("Error Bin file");
                    return false;
                }
            }
            try
            {
                byte[] bootsect = new byte[512];

                using (var fs = System.IO.File.OpenRead(diskfilename))
                {
                    fs.Read(bootsect, 0, 512);
                }
                //把启动代码混合入启动扇区
                bootsect[0] = 0xEB;//JMP
                bootsect[1] = bin[1];

                bootsect[510] = 0x55;//引导标记
                bootsect[511] = 0xAA;

                //混合启动代码
                var addr = bootsect[1] + 2;
                var endpos = Math.Min(0x01fe, bin.Length);
                for (var i = addr; i < endpos; i++)
                {
                    bootsect[i] = bin[i];
                }
                using (var fs = System.IO.File.OpenWrite(diskfilename))
                {
                    fs.Write(bootsect, 0, 512);
                }
                return true;

            }
            catch (Exception err)
            {
                Console.WriteLine(err.Message);
                return false;
            }
        }
        static void ShowHelp()
        {
            Console.WriteLine("这是一个 1.44M 软盘映像的创建工具");
            Console.WriteLine("可以创建一个空的1.44M映像");
            Console.WriteLine("例    fmaker -o d:\\1.vfd");
            Console.WriteLine("也可以创建1.44M映像,并且植入相应的启动区代码");
            Console.WriteLine("例    fmaker -o d:\\1.vfd -bootcode boot.asm");
            Console.WriteLine("例    fmaker -o d:\\1.vfd -bootbin boot.bin");
            Console.WriteLine("如果-o 对应的文件已经存在,并且容量大于等于1.44M，则会认为他是一个已经有文件的映像文件，只会植入启动区，不会破坏他里面的文件，并删除多于1.44M的部分");

        }
        static void FormatF12Floppy(byte[] image)
        {
            using (var ms = new System.IO.MemoryStream(image))
            {
                using (var writer = new System.IO.BinaryWriter(ms))
                {
                    writer.Write((byte)0xeb);//必须JMP开头，才认分区
                    writer.Write((byte)0x3c);//地址无所谓

                    writer.Write((byte)0x90);      //DB 0x90
                    var fatname = System.Text.Encoding.ASCII.GetBytes("TTOSDISK");//必须八字节
                    if (fatname.Length != 8) throw new Exception("error name len,must=8");
                    writer.Write(fatname); //DB      "HELLOIPL"; 启动扇区名称（8字节）
                    writer.Write((Int16)512);     //DW      512; 每个扇区（sector）大小（必须512字节）
                    writer.Write((byte)1);          //DB      1; 簇（cluster）大小（必须为1个扇区）
                    writer.Write((Int16)1);          //DW      1; FAT起始位置（一般为第一个扇区）
                    writer.Write((byte)2);          //DB      2; FAT个数（必须为2）
                    writer.Write((Int16)224);          //DW      224; 根目录大小（一般为224项）
                    writer.Write((Int16)2880);          //DW      2880; 该磁盘大小（必须为2880扇区1440 * 1024 / 512）
                    writer.Write((byte)0xf0);          //DB      0xf0; 磁盘类型（必须为0xf0）
                    writer.Write((Int16)9);          //DW      9; FAT的长度（必须是9扇区）
                    writer.Write((Int16)18);          //DW      18; 一个磁道（track）有几个扇区（必须为18）
                    writer.Write((Int16)2);          //DW      2; 磁头数（必须是2）
                    writer.Write((Int32)0);          //DD      0; 不使用分区，必须是0
                    writer.Write((Int32)2880);   //和格式化磁盘不同//0000       //DD      2880; 重写一次磁盘大小
                    writer.Write((byte)0);          //DB      0,0,0x29; 意义不明（固定）
                    writer.Write((byte)0);
                    writer.Write((byte)0x29);
                    writer.Write((UInt32)0xffffffff);     //和格式化磁盘不同0x20ffd0e9     //DD      0xffffffff; （可能是）卷标号码
                    var diskname = System.Text.Encoding.ASCII.GetBytes("TTOSDISK   ");//必须11字节
                    if (diskname.Length != 11) throw new Exception("error name len,must=11");
                    writer.Write(diskname);          //DB      "HELLO-OS   "; 磁盘的名称（必须为11字节，不足填空格）
                    var fatfname = System.Text.Encoding.ASCII.GetBytes("FAT12   ");//必须11字节
                    if (fatfname.Length != 8) throw new Exception("error name len,must=8");
                    writer.Write(fatfname);          //DB      "FAT12   "; 磁盘格式名称（必须是8字节，不足填空格）

                    var pos = writer.BaseStream.Position;
                    if (pos != 0x3e)
                        throw new Exception("error format.");

                    writer.Seek(0x0200, System.IO.SeekOrigin.Begin);
                    writer.Write((byte)0xf0);
                    writer.Write((byte)0xff);
                    writer.Write((byte)0xff);
                    writer.Seek(0x1400, System.IO.SeekOrigin.Begin);
                    writer.Write((byte)0xf0);
                    writer.Write((byte)0xff);
                    writer.Write((byte)0xff);


                    //此时位置应该为0x3e
                }
            }

            //DB      0xeb, 0x4e, 0x90
            //DB      "HELLOIPL"; 启动扇区名称（8字节）
            //DW      512; 每个扇区（sector）大小（必须512字节）
            //DB      1; 簇（cluster）大小（必须为1个扇区）
            //DW      1; FAT起始位置（一般为第一个扇区）
            //DB      2; FAT个数（必须为2）
            //DW      224; 根目录大小（一般为224项）
            //DW      2880; 该磁盘大小（必须为2880扇区1440 * 1024 / 512）
            //DB      0xf0; 磁盘类型（必须为0xf0）
            //DW      9; FAT的长度（必须是9扇区）
            //DW      18; 一个磁道（track）有几个扇区（必须为18）
            //DW      2; 磁头数（必须是2）
            //DD      0; 不使用分区，必须是0
            //DD      2880; 重写一次磁盘大小
            //DB      0,0,0x29; 意义不明（固定）
            //DD      0xffffffff; （可能是）卷标号码
            //DB      "HELLO-OS   "; 磁盘的名称（必须为11字节，不足填空格）
            //DB      "FAT12   "; 磁盘格式名称（必须是8字节，不足填空格）
        }
        const int DiskSize = 2 * 80 * 18 * 512;  //1.44M软盘容量          //两磁头 80柱面 18扇区 每扇区512字节

        static  bool TryCreateFloppy(string floopyfile)
        {
            try
            {
                if (System.IO.File.Exists(floopyfile))
                {
                    System.IO.FileInfo finfo = new System.IO.FileInfo(floopyfile);
                    if (finfo.Length < DiskSize)
                    {
                        Console.WriteLine("长度不对，重做文件");
                        byte[] diskimage = new byte[DiskSize];
                        FormatF12Floppy(diskimage);//这不是一个磁盘映像，重制他
                        System.IO.File.Delete(floopyfile);
                        System.IO.File.WriteAllBytes(floopyfile, diskimage);
                        return true;
                    }
                    else if (finfo.Length == DiskSize)
                    {
                        Console.WriteLine("长度匹配，跳过");

                        //do nothing
                        return true;
                    }
                    else
                    {
                        byte[] diskimage = new byte[DiskSize];
                        Console.WriteLine("超长，截断");

                        using (var s = finfo.OpenRead())
                        {
                            s.Read(diskimage, 0, DiskSize);//保留这个磁盘映像
                        }
                        System.IO.File.Delete(floopyfile);
                        System.IO.File.WriteAllBytes(floopyfile, diskimage);
                        return true;
                    }
                }
                else
                {
                    Console.WriteLine("创建新文件");

                    byte[] diskimage = new byte[DiskSize];
                    FormatF12Floppy(diskimage);
                    System.IO.File.WriteAllBytes(floopyfile, diskimage);
                    return true;
                }
            }
            catch (Exception err)
            {
                Console.WriteLine(err.Message);
                return false;
            }
        }
        static bool TryGetArgs(string[] args, out string floppyfile, out string bootcodefile, out string bootbinfile)
        {
            floppyfile = null;
            bootcodefile = null;
            bootbinfile = null;
            try
            {
                for (var i = 0; i < args.Length; i++)
                {
                    var tag = args[i].ToLower();
                    if (tag == "-o")
                    {
                        if (floppyfile != null) throw new Exception("Error:have more than one outputfile.");
                        floppyfile = args[i + 1];
                        i++;
                        continue;
                    }
                    else if (tag == "-bootcode")
                    {
                        if (bootcodefile != null || bootbinfile != null) throw new Exception("Error:have more than one bootfile.");
                        bootcodefile = args[i + 1];
                        i++;
                        continue;
                    }
                    else if (tag == "-bootbin")
                    {
                        if (bootcodefile != null || bootbinfile != null) throw new Exception("Error:have more than one bootfile.");
                        bootbinfile = args[i + 1];
                        i++;
                        continue;
                    }
                }
                if (floppyfile == null)
                    throw new Exception("no set floppyfile");
                return true;
            }
            catch
            {
                return false;
            }

        }
    }
}
